#include "net.h"
#include "ip.h"
#include "ethernet.h"
#include "arp.h"
#include "icmp.h"

/**
 * @brief 处理一个收到的数据包
 * 
 * @param buf 要处理的数据包
 * @param src_mac 源mac地址
 */
#define GET_IP_MORE_FRAGMENT(x) (x << 13) //ip分片mf位
const uint8_t local_ip[NET_IP_LEN]=NET_IF_IP;
const int MAX_IP_DATA_LEN=1480;
int ip_id=0;
const int PER_LEN =185;
void ip_in(buf_t *buf, uint8_t *src_mac)
{
    // 头部长度检查
    if (buf->len<sizeof(ip_hdr_t))
    {
        return;
    }
    ip_hdr_t* ip_head = (ip_hdr_t*) buf->data;
    // 协议号和ip数据报长度检查
    if (ip_head->version!=4 || swap16(ip_head->total_len16)>buf->len)
    {
        return;
    }
    // 校验和检查
    uint16_t origin_checksum = ip_head->hdr_checksum16;
    ip_head->hdr_checksum16=0;
    uint16_t check_res=checksum16((uint16_t*)ip_head,ip_head->hdr_len*4);
    if(check_res!=origin_checksum){
        return ;
    }else{
        ip_head->hdr_checksum16=origin_checksum;
    }

    // ip地址比对
    // 错误点：猜测是des_ip无法知道str结束位置
    // if (strcmp(ip_head->dst_ip,local_ip)!=0)
    // {
    //     return;
    // }

    for (int i = 0; i < NET_IP_LEN; i++)
    {
        if (ip_head->dst_ip[i]!=local_ip[i])
        {
            return;
        }
    }
    
    
    // 感觉这里的remove_padding和remove_header 可以合并，
    // 其实两个函数的差别就是buf_remove_header移动了data指针，buf_remove_padding没有

    // 错误点：此处要remove的是数据包数据部分尾部的padding，而不是头部尾部的padding
    // 错误代码：
    // if (swap16(ip_head->hdr_len*4>sizeof(ip_hdr_t)))
    // {
    //     buf_remove_padding(buf,ip_head->hdr_len*4-sizeof(ip_hdr_t));
    // }

    // 修改后正确代码：
    if (swap16(ip_head->total_len16)<buf->len)
    {
        buf_remove_padding(buf,buf->len-swap16(ip_head->total_len16));
    }
    // 错误点：icmp_unreachable需要ip首部的header，不能删。但是net_in不需要。所以在发送icmp_unreachable的时候给头部加回来
    // (反正只是删了len，真正的数据还是在内存中保留着的)
    buf_remove_header(buf,sizeof(ip_hdr_t));
    int res = net_in(buf,ip_head->protocol,ip_head->src_ip);
    if (res==-1)
    {
        buf_add_header(buf,sizeof(ip_hdr_t));
        icmp_unreachable(buf,ip_head->src_ip,ICMP_CODE_PROTOCOL_UNREACH);
    }
}

/**
 * @brief 处理一个要发送的ip分片
 * 
 * @param buf 要发送的分片
 * @param ip 目标ip地址
 * @param protocol 上层协议
 * @param id 数据包id
 * @param offset 分片offset，必须被8整除
 * @param mf 分片mf标志，是否有下一个分片
 */
void ip_fragment_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol, int id, uint16_t offset, int mf)
{
    // 默认没有附加头部
    buf_add_header(buf,sizeof(ip_hdr_t));
    ip_hdr_t* ip_head=(ip_hdr_t*) buf->data;
    memcpy(ip_head->dst_ip,ip,NET_IP_LEN);
    memcpy(ip_head->src_ip,local_ip,NET_IP_LEN);
    ip_head->hdr_len=5;
    ip_head->version=4;
    ip_head->tos=0;
    ip_head->total_len16=swap16(buf->len);
    ip_head->ttl=64;

    ip_head->protocol =protocol;
    // 错误点：id忘了swap
    ip_head->id16=swap16(id);
    // offset和mf是占用同一个uint_16的，其中mf在第14位，1-13位是offset
    int flags_fragment16 = GET_IP_MORE_FRAGMENT(mf)+offset;
    // 错误点：这个是在里面填写的，需要swap
    ip_head->flags_fragment16 =swap16(flags_fragment16);
    ip_head->hdr_checksum16=0;
    // 发的时候都将头部长度设置为20
    uint16_t csum = checksum16((uint16_t*) ip_head, sizeof(ip_hdr_t));
    ip_head->hdr_checksum16 = csum;
    arp_out(buf,ip);
}

/**
 * @brief 处理一个要发送的ip数据包
 * 
 * @param buf 要处理的包
 * @param ip 目标ip地址
 * @param protocol 上层协议
 */
void ip_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol)
{
    // 分片发送
    if (buf->len>MAX_IP_DATA_LEN)
    {
        int res_len=buf->len;
        int start_offset = 0;
        buf_t buf_part;
        // 处理非最后一个分片
        while (res_len>MAX_IP_DATA_LEN)
        {
            // 初始化分片buf并且将原buf中从start开始的1480的Byte复制过来
            buf_init(&buf_part,MAX_IP_DATA_LEN);
            memcpy(buf_part.data,buf->data+start_offset*8,MAX_IP_DATA_LEN);
            ip_fragment_out(&buf_part,ip,protocol,ip_id,start_offset,1);
            res_len-=MAX_IP_DATA_LEN;
            start_offset+=PER_LEN;
        }
        // 处理最后一个分片
        buf_init(&buf_part,res_len);
        memcpy(buf_part.data,buf->data+start_offset*8,res_len);
        // 在最后一个分片的发送的时候将ip_id
        ip_fragment_out(&buf_part,ip,protocol,ip_id++,start_offset,0);
    }else{
        ip_fragment_out(buf,ip,protocol,ip_id++,0,0);
    }
}

/**
 * @brief 初始化ip协议
 * 
 */
void ip_init()
{
    net_add_protocol(NET_PROTOCOL_IP, ip_in);
}